/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing.tree;

import java.io.*;
import java.beans.ConstructorProperties;

/**
 * {@code TreePath} represents an array of objects that uniquely
 * identify the path to a node in a tree. The elements of the array
 * are ordered with the root as the first element of the array. For
 * example, a file on the file system is uniquely identified based on
 * the array of parent directories and the name of the file. The path
 * {@code /tmp/foo/bar} could be represented by a {@code TreePath} as
 * {@code new TreePath(new Object[] {"tmp", "foo", "bar"})}.
 * <p>
 * {@code TreePath} is used extensively by {@code JTree} and related classes.
 * For example, {@code JTree} represents the selection as an array of
 * {@code TreePath}s. When used with {@code JTree}, the elements of the
 * path are the objects returned from the {@code TreeModel}. When {@code JTree}
 * is paired with {@code DefaultTreeModel}, the elements of the
 * path are {@code TreeNode}s. The following example illustrates extracting
 * the user object from the selection of a {@code JTree}:
 * <pre>
 *   DefaultMutableTreeNode root = ...;
 *   DefaultTreeModel model = new DefaultTreeModel(root);
 *   JTree tree = new JTree(model);
 *   ...
 *   TreePath selectedPath = tree.getSelectionPath();
 *   DefaultMutableTreeNode selectedNode =
 *       ((DefaultMutableTreeNode)selectedPath.getLastPathComponent()).
 *       getUserObject();
 * </pre>
 * Subclasses typically need override only {@code
 * getLastPathComponent}, and {@code getParentPath}. As {@code JTree}
 * internally creates {@code TreePath}s at various points, it's
 * generally not useful to subclass {@code TreePath} and use with
 * {@code JTree}.
 * <p>
 * While {@code TreePath} is serializable, a {@code
 * NotSerializableException} is thrown if any elements of the path are
 * not serializable.
 * <p>
 * For further information and examples of using tree paths,
 * see <a
 * href="https://docs.oracle.com/javase/tutorial/uiswing/components/tree.html">How to Use Trees</a>
 * in <em>The Java Tutorial.</em>
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans&trade;
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author Scott Violet
 * @author Philip Milne
 */
public class TreePath extends Object implements Serializable {

  /**
   * Path representing the parent, null if lastPathComponent represents
   * the root.
   */
  private TreePath parentPath;
  /**
   * Last path component.
   */
  private Object lastPathComponent;

  /**
   * Creates a {@code TreePath} from an array. The array uniquely
   * identifies the path to a node.
   *
   * @param path an array of objects representing the path to a node
   * @throws IllegalArgumentException if {@code path} is {@code null}, empty, or contains a {@code
   * null} value
   */
  @ConstructorProperties({"path"})
  public TreePath(Object[] path) {
    if (path == null || path.length == 0) {
      throw new IllegalArgumentException("path in TreePath must be non null and not empty.");
    }
    lastPathComponent = path[path.length - 1];
    if (lastPathComponent == null) {
      throw new IllegalArgumentException(
          "Last path component must be non-null");
    }
    if (path.length > 1) {
      parentPath = new TreePath(path, path.length - 1);
    }
  }

  /**
   * Creates a {@code TreePath} containing a single element. This is
   * used to construct a {@code TreePath} identifying the root.
   *
   * @param lastPathComponent the root
   * @throws IllegalArgumentException if {@code lastPathComponent} is {@code null}
   * @see #TreePath(Object[])
   */
  public TreePath(Object lastPathComponent) {
    if (lastPathComponent == null) {
      throw new IllegalArgumentException("path in TreePath must be non null.");
    }
    this.lastPathComponent = lastPathComponent;
    parentPath = null;
  }

  /**
   * Creates a {@code TreePath} with the specified parent and element.
   *
   * @param parent the path to the parent, or {@code null} to indicate the root
   * @param lastPathComponent the last path element
   * @throws IllegalArgumentException if {@code lastPathComponent} is {@code null}
   */
  protected TreePath(TreePath parent, Object lastPathComponent) {
    if (lastPathComponent == null) {
      throw new IllegalArgumentException("path in TreePath must be non null.");
    }
    parentPath = parent;
    this.lastPathComponent = lastPathComponent;
  }

  /**
   * Creates a {@code TreePath} from an array. The returned
   * {@code TreePath} represents the elements of the array from
   * {@code 0} to {@code length - 1}.
   * <p>
   * This constructor is used internally, and generally not useful outside
   * of subclasses.
   *
   * @param path the array to create the {@code TreePath} from
   * @param length identifies the number of elements in {@code path} to create the {@code TreePath}
   * from
   * @throws NullPointerException if {@code path} is {@code null}
   * @throws ArrayIndexOutOfBoundsException if {@code length - 1} is outside the range of the array
   * @throws IllegalArgumentException if any of the elements from {@code 0} to {@code length - 1}
   * are {@code null}
   */
  protected TreePath(Object[] path, int length) {
    lastPathComponent = path[length - 1];
    if (lastPathComponent == null) {
      throw new IllegalArgumentException(
          "Path elements must be non-null");
    }
    if (length > 1) {
      parentPath = new TreePath(path, length - 1);
    }
  }

  /**
   * Creates an empty {@code TreePath}.  This is provided for
   * subclasses that represent paths in a different
   * manner. Subclasses that use this constructor must override
   * {@code getLastPathComponent}, and {@code getParentPath}.
   */
  protected TreePath() {
  }

  /**
   * Returns an ordered array of the elements of this {@code TreePath}.
   * The first element is the root.
   *
   * @return an array of the elements in this {@code TreePath}
   */
  public Object[] getPath() {
    int i = getPathCount();
    Object[] result = new Object[i--];

    for (TreePath path = this; path != null; path = path.getParentPath()) {
      result[i--] = path.getLastPathComponent();
    }
    return result;
  }

  /**
   * Returns the last element of this path.
   *
   * @return the last element in the path
   */
  public Object getLastPathComponent() {
    return lastPathComponent;
  }

  /**
   * Returns the number of elements in the path.
   *
   * @return the number of elements in the path
   */
  public int getPathCount() {
    int result = 0;
    for (TreePath path = this; path != null; path = path.getParentPath()) {
      result++;
    }
    return result;
  }

  /**
   * Returns the path element at the specified index.
   *
   * @param index the index of the element requested
   * @return the element at the specified index
   * @throws IllegalArgumentException if the index is outside the range of this path
   */
  public Object getPathComponent(int index) {
    int pathLength = getPathCount();

    if (index < 0 || index >= pathLength) {
      throw new IllegalArgumentException("Index " + index +
          " is out of the specified range");
    }

    TreePath path = this;

    for (int i = pathLength - 1; i != index; i--) {
      path = path.getParentPath();
    }
    return path.getLastPathComponent();
  }

  /**
   * Compares this {@code TreePath} to the specified object. This returns
   * {@code true} if {@code o} is a {@code TreePath} with the exact
   * same elements (as determined by using {@code equals} on each
   * element of the path).
   *
   * @param o the object to compare
   */
  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    if (o instanceof TreePath) {
      TreePath oTreePath = (TreePath) o;

      if (getPathCount() != oTreePath.getPathCount()) {
        return false;
      }
      for (TreePath path = this; path != null;
          path = path.getParentPath()) {
        if (!(path.getLastPathComponent().equals
            (oTreePath.getLastPathComponent()))) {
          return false;
        }
        oTreePath = oTreePath.getParentPath();
      }
      return true;
    }
    return false;
  }

  /**
   * Returns the hash code of this {@code TreePath}. The hash code of a
   * {@code TreePath} is the hash code of the last element in the path.
   *
   * @return the hashCode for the object
   */
  public int hashCode() {
    return getLastPathComponent().hashCode();
  }

  /**
   * Returns true if <code>aTreePath</code> is a
   * descendant of this
   * {@code TreePath}. A {@code TreePath} {@code P1} is a descendant of a
   * {@code TreePath} {@code P2}
   * if {@code P1} contains all of the elements that make up
   * {@code P2's} path.
   * For example, if this object has the path {@code [a, b]},
   * and <code>aTreePath</code> has the path {@code [a, b, c]},
   * then <code>aTreePath</code> is a descendant of this object.
   * However, if <code>aTreePath</code> has the path {@code [a]},
   * then it is not a descendant of this object.  By this definition
   * a {@code TreePath} is always considered a descendant of itself.
   * That is, <code>aTreePath.isDescendant(aTreePath)</code> returns
   * {@code true}.
   *
   * @param aTreePath the {@code TreePath} to check
   * @return true if <code>aTreePath</code> is a descendant of this path
   */
  public boolean isDescendant(TreePath aTreePath) {
    if (aTreePath == this) {
      return true;
    }

    if (aTreePath != null) {
      int pathLength = getPathCount();
      int oPathLength = aTreePath.getPathCount();

      if (oPathLength < pathLength)
      // Can't be a descendant, has fewer components in the path.
      {
        return false;
      }
      while (oPathLength-- > pathLength) {
        aTreePath = aTreePath.getParentPath();
      }
      return equals(aTreePath);
    }
    return false;
  }

  /**
   * Returns a new path containing all the elements of this path
   * plus <code>child</code>. <code>child</code> is the last element
   * of the newly created {@code TreePath}.
   *
   * @param child the path element to add
   * @throws NullPointerException if {@code child} is {@code null}
   */
  public TreePath pathByAddingChild(Object child) {
    if (child == null) {
      throw new NullPointerException("Null child not allowed");
    }

    return new TreePath(this, child);
  }

  /**
   * Returns the {@code TreePath} of the parent. A return value of
   * {@code null} indicates this is the root node.
   *
   * @return the parent path
   */
  public TreePath getParentPath() {
    return parentPath;
  }

  /**
   * Returns a string that displays and identifies this
   * object's properties.
   *
   * @return a String representation of this object
   */
  public String toString() {
    StringBuffer tempSpot = new StringBuffer("[");

    for (int counter = 0, maxCounter = getPathCount(); counter < maxCounter;
        counter++) {
      if (counter > 0) {
        tempSpot.append(", ");
      }
      tempSpot.append(getPathComponent(counter));
    }
    tempSpot.append("]");
    return tempSpot.toString();
  }
}
